﻿#include "libcomm.h"
#ifndef COMM_MSVC 
#include <dlfcn.h>
#include <dirent.h>
//#ifdef COMM_X64
#define _fseeki64	fseeko
#define _ftelli64	ftello
//#else
//#define _fseeki64	fseek
//#define _ftelli64	ftell
//#endif
#define _access		access
File::WriteUnMasker::WriteUnMasker() { m_mask = umask(0); }
File::WriteUnMasker::~WriteUnMasker() { umask(m_mask); }
#endif

bool File::Enum(const char* dir_, const char* prefix, StringList& names, int postfix)
{
#ifdef __GNUC__
	DIR *dir = NULL;
	struct dirent* ptr = NULL;

	if ((dir = opendir(dir_)) == NULL)
	{
		LOGFUNCERROR(opendir);
		return false;
	}

	while ((ptr = readdir(dir)) != NULL)
	{
		if (strcmp(ptr->d_name, ".") == 0 || strcmp(ptr->d_name, "..") == 0)
			continue;
		else if (ptr->d_type == 8 || ptr->d_type == 10 || ptr->d_type == 0)    ///file or link file.
		{
			String fileName = ptr->d_name;
			bool found = false;
			{
				if (postfix)
					found = fileName.endwith(prefix);
				else
					found = fileName.startwith(prefix);
			}

			if (found)
				names << fileName;
		}
	}
	closedir(dir);
	return true;

#else

	WIN32_FIND_DATA FileData;
	HANDLE hSearch;
	BOOL fFinished = FALSE;
	String sFileName;
	if (postfix)
		sFileName = String::format("%s\\*%s", dir_, prefix);
	else
		sFileName = String::format("%s\\%s*", dir_, prefix);

	hSearch = FindFirstFile(sFileName, &FileData);
	if (INVALID_HANDLE_VALUE == hSearch)
	{
		LOGERR("FindFirstFile(%s) error: (%d) %s", MYLIB_LOGSTR(sFileName), MYLIB_ERRNO, MYLIB_STRERROR(MYLIB_ERRNO));
		return false;
	}
	while (!fFinished)
	{
		names << FileData.cFileName;

		if (!FindNextFile(hSearch, &FileData))
		{
			if (GetLastError() == ERROR_NO_MORE_FILES)
			{
				fFinished = TRUE;
				continue;
			}
			LOGFUNCERROR(FindNextFile);
			break;
		}
	}
	if (!FindClose(hSearch))
	{
		LOGFUNCERROR(FindClose);
	}
	return true;

#endif
}

bool File::MakeDir(const char* dir, int mode)
{
	if (!Access(dir, F_OK))
	{
#ifndef COMM_MSVC
		WriteUnMasker masker;
		if (0 != mkdir(dir, mode))
		{
			LOGERR("mkdir \"%s\" error: (%d) %s", MYLIB_LOGSTR(dir), errno, strerror(errno));
			return false;
		}
#else
		mode = 0;
		if (0 != _mkdir(dir))
		{
			LOGERR("mkdir \"%s\" error: (%d) %s", MYLIB_LOGSTR(dir), errno, strerror(errno));
			return false;
		}
#endif
	}
	return true;
}

bool File::ChangeMode(const char* src, int mode)
{
#ifndef COMM_MSVC
	if (0 != chmod(src, mode))
	{
		LOGERR("chmod \"%s\" to mode(%04o) error: (%d) %s", src, mode, errno, strerror(errno));
		return false;
	}
#endif
	return true;
}

bool File::Access(const char* src, int mode)
{
	return (0 == _access(src, mode));
}

bool File::Copy(const char* src, const char* dest, size_t block_size)
{
	Buffer block(block_size);
	File f1(src, "rb");
	File f2(dest, "wb");
	if (!f1.IsOpened() || !f2.IsOpened())
		return false;
	do
	{
		if (0 == f1.Read(block))
			break;
		if (!f2.Write(block))
			return false;
	} while (true);
	return true;
}

bool File::Move(const char* src, const char* dest)
{
	if (!Copy(src, dest))
		return false;
	return Remove(src);
}

bool File::Remove(const char* src)
{
#ifndef COMM_MSVC
	if (0 != unlink(src))
	{
		LOGFUNCERROR(unlink);
		return false;
	}
#else
	if (!DeleteFileA(src))
	{
		LOGFUNCERROR(DeleteFile);
		return false;
	}
#endif
	return true;
}

Buffer File::LoadContent(const char* filename)
{
	Buffer content;
	File f;
	if (!f.Open(filename, "rb") || !f.Load(content))
		content.resize(0);
	return content;
}

String File::LoadContentText(const char* filename)
{
	Buffer content(1);
	File f;
	if (!f.Open(filename, "r") || !f.LoadText(content)) {}
	return content.get();
}

bool File::SaveContent(const char* filename, const void* buffer, size_t size)
{
	File f;
	return (f.Open(filename, "wb") && f.Write(buffer, size));
}


File::File() :m_stream(NULL)
{
}

File::File(const char* filename, const char* mode) : m_stream(NULL)
{
	this->Open(filename, mode);
}

File::~File()
{
	this->Close();
}

bool File::Open(const char* filename, const char* mode)
{
	ThreadLockGuard guard(&m_lock);
	if (!this->Close())
		return false;
	m_filename = filename;
	MYLIB_ASSERT(NULL == m_stream);
	m_stream = fopen(m_filename, mode);
	if (NULL == m_stream)
	{
		LOGERR("open file \"%s\" in mode \"%s\" error: (%d) %s", MYLIB_LOGSTR(this->Name()), mode, errno, strerror(errno));
		return false;
	}

	return true;
}

bool File::Close()
{
	ThreadLockGuard guard(&m_lock);
	if (NULL != m_stream)
	{
		if (0 != fclose(m_stream))
		{
			LOGERR("close file \"%s\" error: (%d) %s", MYLIB_LOGSTR(this->Name()), errno, strerror(errno));
			return false;
		}
		m_stream = NULL;
	}
	return true;
}

FILE* File::Stream()
{
	ThreadLockGuard guard(&m_lock);
	return m_stream;
}

const char* File::Name()
{
	ThreadLockGuard guard(&m_lock);
	return (char*)m_filename;
}

bool File::IsOpened()
{
	ThreadLockGuard guard(&m_lock);
	return (NULL != m_stream);
}

long long File::Size()
{
	ThreadLockGuard guard(&m_lock);
	MYLIB_ASSERT(IsOpened());
	MYLIB_ASSERT(this->MoveToEnd());
	return this->Position();
}

long long File::Position()
{
	ThreadLockGuard guard(&m_lock);
	MYLIB_ASSERT(IsOpened());
	long long pos = _ftelli64(m_stream);
	MYLIB_ASSERT(-1 != pos);
	return pos;
}

bool File::MoveTo(long long position)
{
	ThreadLockGuard guard(&m_lock);
	MYLIB_ASSERT(IsOpened());
	if (0 != _fseeki64(m_stream, position, SEEK_SET))
	{
		LOGERR("seeking file \"%s\" error: (%d) %s", MYLIB_LOGSTR(this->Name()), errno, strerror(errno));
		return false;
	}
	return true;
}

bool File::MoveToBegin()
{
	ThreadLockGuard guard(&m_lock);
	MYLIB_ASSERT(IsOpened());
	if (0 != _fseeki64(m_stream, 0, SEEK_SET))
	{
		LOGERR("seeking file \"%s\" error: (%d) %s", MYLIB_LOGSTR(this->Name()), errno, strerror(errno));
		return false;
	}
	//rewind(m_stream);
	return true;
}

bool File::MoveToEnd()
{
	ThreadLockGuard guard(&m_lock);
	MYLIB_ASSERT(IsOpened());
	if (0 != _fseeki64(m_stream, 0, SEEK_END))
	{
		LOGERR("seeking file \"%s\" error: (%d) %s", MYLIB_LOGSTR(this->Name()), errno, strerror(errno));
		return false;
	}
	return true;
}


size_t File::Read(void* buffer, size_t size)
{
	ThreadLockGuard guard(&m_lock);
	size_t bytes = 0;
	if (IsOpened() && !feof(m_stream))
	{
		bytes = fread(buffer, sizeof(char), size, m_stream);
		if (ferror(m_stream))
		{
			LOGERR("reading file \"%s\" error: (%d) %s", MYLIB_LOGSTR(this->Name()), errno, strerror(errno));
			bytes = 0;
		}
	}
	return bytes;
}

size_t File::Read(Buffer& buffer)
{
	size_t bytes = this->Read(buffer.get(), buffer.size());
	buffer.resize(bytes);
	return bytes;
}

File& File::operator >> (Buffer& buffer)
{
	this->Read(buffer);
	return *this;
}

bool File::Write(const void* buffer, size_t size)
{
	ThreadLockGuard guard(&m_lock);
	if (!IsOpened())
		return false;
	if (1 != fwrite(buffer, size, 1, m_stream))
	{
		LOGERR("writing file \"%s\" error: (%d) %s", MYLIB_LOGSTR(this->Name()), errno, strerror(errno));
		return false;
	}
	return true;
}

bool File::Write(const Buffer& buffer)
{
	return this->Write(buffer.get(), buffer.size());
}

bool File::WriteText(const char* format, ...)
{
	ThreadLockGuard guard(&m_lock);
	if (!IsOpened())
		return false;
	int err;
	va_list args;
	va_start(args, format);
	err = vfprintf(m_stream, format, args);
	va_end(args);
	if (-1 == err)
	{
		LOGFUNCERROR(vfprintf);
		return false;
	}
	return true;
}

File& File::operator <<(const Buffer& buffer)
{
	this->Write(buffer);
	return *this;
}

bool File::Flush()
{
	ThreadLockGuard guard(&m_lock);
	if (!IsOpened())
		return false;
	if (0 != fflush(m_stream))
	{
		LOGERR("flushing file \"%s\" error: (%d) %s", MYLIB_LOGSTR(this->Name()), errno, strerror(errno));
		return false;
	}
	return true;
}

bool File::Load(Buffer& content)
{
	return LoadToMemory(content, false);
}

bool File::LoadText(Buffer& content)
{
	return LoadToMemory(content, true);
}

bool File::LoadToMemory(Buffer& content, bool asText)
{
	ThreadLockGuard guard(&m_lock);
	if (!IsOpened())
		return false;
	size_t count, total;
	count = total = 0;
	long long filesize = this->Size();
	MYLIB_ASSERT(filesize < 0xFFFFFFFF);
	size_t buffersize = filesize & 0xFFFFFFFF;
	content.resize(buffersize + 1); // add ended '\0' for text content and feof().
	if (!this->MoveToBegin())
		return false;
	while (!feof(m_stream))
	{
		count = fread(content.get() + total, sizeof(char), content.size() - total, m_stream);
		if (ferror(m_stream))
		{
			LOGERR("reading file \"%s\" error: (%d) %s", MYLIB_LOGSTR(this->Name()), errno, strerror(errno));
			return false;
		}
		total += count;
	}
	MYLIB_ASSERT(total <= buffersize); // may be less in text mode as "\r\n" would convert to "\n" in windows.
	content[total] = 0;
	content.resize(asText ? (total + 1) : total); // not need ended '\0'.
	return true;
}


